package basicPackage;

import java.util.HashMap;
import java.util.Map;

import be.kuleuven.cs.som.annotate.Basic;
import be.kuleuven.cs.som.annotate.Immutable;
import be.kuleuven.cs.som.annotate.Raw;

/**
 * This class represents a coordinate in a 3 dimensional field with coordinates x, y, z.
 * 
 * @author Laurens Vanderstraeten
 */
public class Coordinate {

	/**
	 * Construct a new Coordinate with the given coordinates x, y, z.
	 * 
	 * @param 	x
	 * 			The x coordinate.
	 * @param	y
	 * 			The y coordinate.
	 * @param	z
	 * 			the z coordinate.
	 * @post	| getX() == x
	 * @post	| getY() == y
	 * @post	| getZ() == z
	 */
	@Raw
	public Coordinate(long x, long y, long z) {
		this.x = x;
		this.y = y;
		this.z = z;
	}
	
	/**
	 * Construct a new Coordinate with the coordinates (0, 0, 0)
	 * 
	 * @effect	| Coordinate(0, 0, 0)
	 */
	@Raw
	public Coordinate() {
		this(0, 0, 0);
	}
	
	/**
	 * Returns the x coordinate.
	 */
	@Basic @Immutable @Raw
	public long getX() {
		return x;
	}
	
	/**
	 * Variable for registering the x coordinate.
	 */
	private long x;
	
	/**
	 * Returns the y coordinate.
	 */
	@Basic @Immutable @Raw
	public long getY() {
		return y;
	}
	
	/**
	 * Variable for registering the y coordinate.
	 */
	private long y;
	
	/**
	 * Returns the z coordinate.
	 */
	@Basic @Immutable @Raw
	public long getZ() {
		return z;
	}
	
	/**
	 * Variable for registering the z coordinate.
	 */
	private long z;
	
	/**
	 * Check whether all coordinates of this coordinate are larger or equal to the coordinates of the
	 * given coordinate
	 * 
	 * @param 	coordinate
	 * 			The coordinate to be compared to this coordinate.
	 * @return	False if one or more coordinates of this coordinate are smaller then the given 
	 * 			coordinate.
	 * @throws	IllegalArgumentException
	 * 			| coordinate == null	
	 */
	@Raw
	public Boolean isLargerOrEqualTo(Coordinate coordinate) throws IllegalArgumentException {
		if(coordinate == null)
			throw new IllegalArgumentException();
		if(!(this.getX() >= coordinate.getX()))
			return false;
		if(!(this.getY() >= coordinate.getY()))
			return false;
		if(!(this.getZ() >= coordinate.getZ()))
			return false;
		return true;
	}
	
	/**
	 * Check whether all coordinates of this coordinate are smaller or equal to the coordinates of the
	 * given coordinate
	 * 
	 * @param 	coordinate
	 * 			The coordinate to be compared to this coordinate.
	 * @return	False if one or more coordinates of this coordinate are larger then the given 
	 * 			coordinate.
	 * @throws	IllegalArgumentException
	 * 			| coordinate == null	
	 */
	@Raw
	public Boolean isSmallerOrEqualTo(Coordinate coordinate) throws IllegalArgumentException {
		if(coordinate == null)
			throw new IllegalArgumentException();
		return coordinate.isLargerOrEqualTo(this);
	}
	
	/**
	 * Check whether this coordinate is positive.
	 * 
	 * @return	False if this coordinate has a negative	value as one of its coordinates.
	 */
	@Immutable @Raw
	public Boolean isPositive() {
		return this.isLargerOrEqualTo(new Coordinate());
	}
	
	/**
	 * Check whether this coordinate has the same value for all its coordinates.
	 * 
	 * @return	True if all the coordinates of this coordinate have the same value.
	 */
	public Boolean hasSameValueForAllCoordinates() {
		return getX() == getY() && getY() == getZ();
	}
	
	/**
	 * Return a map with the neighboring coordinates of this coordinate mapped under the 
	 * corresponding direction.
	 * 
	 * @return	A map with the neighboring coordinates of this coordinate mapped under the 
	 * 			corresponding direction. Coordinates with coordinate values of Long.MAX_VALUE or 
	 * 			Long.MIN_VALUE have no neighboring coordinates with coordinates beyond those values.
	 */
	public Map<Direction, Coordinate> getNeighboringCoordinates() {
		Map<Direction, Coordinate> neighboringCoordinates = new HashMap<Direction, Coordinate>();
		if (getX() != Long.MAX_VALUE)
			neighboringCoordinates.put(Direction.EAST, new Coordinate(getX()+1,getY(),getZ()));
		if (getX() != Long.MIN_VALUE)
			neighboringCoordinates.put(Direction.WEST, new Coordinate(getX()-1,getY(),getZ()));
		if (getY() != Long.MAX_VALUE)
			neighboringCoordinates.put(Direction.NORTH, new Coordinate(getX(),getY()+1,getZ()));
		if (getY() != Long.MIN_VALUE)
			neighboringCoordinates.put(Direction.SOUTH, new Coordinate(getX(),getY()-1,getZ()));
		if (getZ() != Long.MAX_VALUE)
			neighboringCoordinates.put(Direction.CEILING, new Coordinate(getX(),getY(),getZ()+1));
		if (getZ() != Long.MIN_VALUE)
			neighboringCoordinates.put(Direction.FLOOR, new Coordinate(getX(),getY(),getZ()-1));
		return neighboringCoordinates;
	}
	
	/**
	 * Return the coordinate resulting from adding the respective x,y,z coordinates of the this dungeon with the given dungeon.
	 * 
	 * @param 	coordinate
	 * 			The coordinate to be added.
	 * @throws	IllegalArgumentException
	 * 			| coordinate == null
	 */
	public Coordinate add(Coordinate coordinate) throws IllegalArgumentException {
		if(coordinate == null)
			throw new IllegalArgumentException();
		return new Coordinate(getX() + coordinate.getX(), getY() + coordinate.getY(), getZ() + coordinate.getZ());
	}
	
	/**
	 * Return the coordinate resulting from subtracting the respective x,y,z coordinates from this dungeon with the given dungeon.
	 * 
	 * @param 	coordinate
	 * 			The coordinate to subtract.
	 * @throws	IllegalArgumentException
	 * 			| coordinate == null
	 */
	public Coordinate subtract(Coordinate coordinate) throws IllegalArgumentException {
		if(coordinate == null)
			throw new IllegalArgumentException();
		return new Coordinate(getX() - coordinate.getX(), getY() - coordinate.getY(), getZ() - coordinate.getZ());
	}
	
	/** 
	 * Check whether the given obj is equal to this coordinate.
	 * 
	 * @param	obj
	 * 			The object to be checked.
	 * @return	| if(obj == null)
	 *			|	result == false
	 *   		| if(!(obj instanceof Coordinate))
	 *  	    |	result == false
	 *			| if(!(((Coordinate)obj).getX() == getX()))
	 *  	    | 	result == false
	 *   		| if(!(((Coordinate)obj).getY() == getY()))
     *	   		|	result == false
	 *  		| if(!(((Coordinate)obj).getZ() == getZ()))
	 *  	   	|	result == false
     * 			| else then
     * 			|	result == true
	 */
	@Override
	public boolean equals(Object obj) {
		if(obj == null)
			return false;
	    if(!(obj instanceof Coordinate))
	   	   return false;
		if(!(((Coordinate)obj).getX() == getX()))
	   	   return false;
	    if(!(((Coordinate)obj).getY() == getY()))
    	   return false;
	    if(!(((Coordinate)obj).getZ() == getZ()))
	   	   return false;
    return true;
    }
	
	@Override
	public int hashCode(){
		String hashString = String.valueOf(getX())+ "," + String.valueOf(getX())+ "," + String.valueOf(getX());
		return hashString.hashCode();
	}
}
